package com.yanggu.payment.service.impl;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.AlipayConstants;
import com.alipay.api.domain.*;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.*;
import com.alipay.api.response.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yanggu.payment.config.ALiPayConfig;
import com.yanggu.payment.entity.OrderInfo;
import com.yanggu.payment.entity.PaymentInfo;
import com.yanggu.payment.entity.RefundInfo;
import com.yanggu.payment.enums.OrderStatus;
import com.yanggu.payment.enums.PayType;
import com.yanggu.payment.enums.alipay.AliPayTradeState;
import com.yanggu.payment.service.AliPayService;
import com.yanggu.payment.service.OrderInfoService;
import com.yanggu.payment.service.PaymentService;
import com.yanggu.payment.service.RefundInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Map;

/**
 * @version V1.0
 * @author: YangGu
 * @date: 2022/5/10 16:08
 * @description:
 */
@Slf4j
@Service
public class AliPayServiceImpl implements AliPayService {

    @Autowired
    private OrderInfoService orderInfoService;

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private ALiPayConfig aLiPayConfig;

    @Autowired
    private AlipayClient alipayClient;

    @Autowired
    private RefundInfoService refundInfoService;

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public String tradePagePay(Long productId) throws AlipayApiException {

        OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId, PayType.ALIPAY.getType());

        AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
        request.setNotifyUrl(aLiPayConfig.getNotifyUrl());
        request.setReturnUrl(aLiPayConfig.getReturnUrl());
        AlipayTradePagePayModel bizContent = new AlipayTradePagePayModel();

        //设置商户订单号
        bizContent.setOutTradeNo(orderInfo.getOrderNo());
        //设置支付金额(单位元) 需要 / 100
        bizContent.setTotalAmount(NumberUtil.round(NumberUtil.div(orderInfo.getTotalFee().toString(), "100"), 2).toString());
        //设置订单标题
        bizContent.setSubject(orderInfo.getTitle());
        //设置销售产品码, 写死即可
        bizContent.setProductCode("FAST_INSTANT_TRADE_PAY");
        request.setBizModel(bizContent);

        log.info("统一收单下单并支付页面接口, 请求参数: {}", JSON.toJSONString(request));
        AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
        log.info("统一收单下单并支付页面接口, 响应参数: {}", JSON.toJSONString(response));
        if (!response.isSuccess()) {
            throw new RuntimeException("统一收单下单并支付页面接口, 调用失败");
        }
        log.info("统一收单下单并支付页面接口, 调用成功");

        return response.getBody();
    }

    @Override
    public String tradeQuery(String orderNo) throws AlipayApiException {
        AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
        AlipayTradeQueryModel model = new AlipayTradeQueryModel();
        model.setOutTradeNo(orderNo);
        request.setBizModel(model);
        //这里需要注意的是, 微信调用native api后就会创建订单。和微信不同的是, 只有当用户扫码或者登录支付宝账号后, 支付宝才会创建订单。否则是查询不到订单的
        AlipayTradeQueryResponse response = alipayClient.execute(request);
        System.out.println(response.getBody());
        if (response.isSuccess()) {
            System.out.println("调用成功");
        } else {
            System.out.println("调用失败");
        }
        return response.getBody();
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public String tradeNotify(Map<String, String> paramsMap) throws AlipayApiException {

        //调用SDK验证签名
        String result = "FAILURE";
        log.info("支付宝回调参数: {}", JSON.toJSONString(paramsMap));
        boolean signVerified = AlipaySignature.rsaCheckV1(paramsMap,
                aLiPayConfig.getAlipayPublicKey(),
                AlipayConstants.CHARSET_UTF8,
                AlipayConstants.SIGN_TYPE_RSA2);
        if (!signVerified) {
            //验签失败则记录异常日志，并在response中返回failure.
            log.error("支付宝回调验签失败, {}", JSON.toJSONString(paramsMap));
            return result;
        }

        //验签成功后，按照支付结果异步通知中的描述，对支付结果中的业务内容进行二次校验，校验成功后在response中返回success并继续商户自身业务处理，校验失败返回failure
        //商户需要验证该通知数据中的 out_trade_no 是否为商户系统中创建的订单号；
        String orderNo = paramsMap.get("out_trade_no");
        OrderInfo dbOrderInfo = orderInfoService.queryByOrderNo(orderNo);
        if (dbOrderInfo == null) {
            log.error("支付宝回调验签失败, orderNo不存在, 订单号: {}", orderNo);
            return result;
        }

        //判断 total_amount 是否确实为该订单的实际金额（即商户订单创建时的金额）；单位元
        String totalAmount = paramsMap.get("total_amount");
        if (!dbOrderInfo.getTotalFee().equals(NumberUtil.mul(totalAmount, "100").intValue())) {
            log.error("支付宝回调验签失败, 订单金额和支付金额不一致, totalAmount: {}", totalAmount);
            return result;
        }

        //校验通知中的 seller_id（或者 seller_email) 是否为 out_trade_no 这笔单据的对应的操作方（有的时候，一个商户可能有多个 seller_id/seller_email）；
        String sellerId = paramsMap.get("seller_id");
        if (!StrUtil.equals(aLiPayConfig.getSellerId(), sellerId)) {
            log.error("商家支付宝账号验证失败, 商家支付宝账号不一致, sellerId: {}", sellerId);
            return result;
        }

        //验证 app_id 是否为该商户本身。
        String appId = paramsMap.get("app_id");
        if (!StrUtil.equals(aLiPayConfig.getAppId(), appId)) {
            log.error("商户appId验证失败, appId不一致: appId: {}", appId);
            return result;
        }
        
        //处理商家自己的业务数据
        
        //修改订单状态
        if (!StrUtil.equals(OrderStatus.NOTPAY.getType(), dbOrderInfo.getOrderStatus())) {
            log.warn("已经处理过支付宝支付通知: 订单号: {}", orderNo);
            return result;
        }
        Boolean businessResult = handlerPaySuccess(dbOrderInfo, paramsMap.get("trade_no"), paramsMap.get("trade_status"));
        if (Boolean.FALSE.equals(businessResult)) {
            return result;
        }
        result = "SUCCESS";

        return result;
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public Boolean handlerPaySuccess(OrderInfo dbOrderInfo,
                                     String tradeNo,
                                     String tradeStatus) {
        String orderNo = dbOrderInfo.getOrderNo();
        //修改订单状态
        OrderInfo updateOrderInfo = new OrderInfo();
        updateOrderInfo.setOrderStatus(OrderStatus.SUCCESS.getType());
        //添加幂等性判断。更新时间是否相等, 状态是否变更
        LambdaQueryWrapper<OrderInfo> queryWrapper = Wrappers.<OrderInfo>lambdaQuery()
                .eq(OrderInfo::getOrderNo, orderNo)
                .eq(OrderInfo::getOrderStatus, OrderStatus.NOTPAY.getType())
                .eq(OrderInfo::getUpdateTime, dbOrderInfo.getUpdateTime());
        boolean updateResult = orderInfoService.update(updateOrderInfo, queryWrapper);
        if (!updateResult) {
            log.warn("支付宝支付回调订单状态更新失败: 订单号: {}", orderNo);
            return false;
        }
        log.info("支付宝支付, 订单状态更新为支付成功, 订单号: {}", orderNo);
        //生成支付记录表
        PaymentInfo paymentInfo = new PaymentInfo();
        paymentInfo.setOrderNo(orderNo);
        paymentInfo.setTransactionId(tradeNo);
        paymentInfo.setPaymentType(PayType.ALIPAY.getType());
        paymentInfo.setTradeType("电脑网站支付");
        paymentInfo.setTradeState(tradeStatus);
        paymentInfo.setPayerTotal(dbOrderInfo.getTotalFee());

        paymentService.save(paymentInfo);
        return true;
    }

    @Override
    public void tradeClose(String orderNo) throws AlipayApiException {
        AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
        AlipayTradeCloseModel model = new AlipayTradeCloseModel();
        model.setOutTradeNo(orderNo);
        request.setBizModel(model);
        AlipayTradeCloseResponse response = alipayClient.execute(request);
        System.out.println(response.getBody());
        if (response.isSuccess()) {
            System.out.println("调用成功");
        } else {
            System.out.println("调用失败");
        }
        //修改订单状态为关闭
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setOrderStatus(OrderStatus.CANCEL.getType());
        LambdaQueryWrapper<OrderInfo> wrapper = Wrappers.<OrderInfo>lambdaQuery()
                .eq(OrderInfo::getOrderNo, orderNo);
        orderInfoService.update(orderInfo, wrapper);
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void refunds(String orderNo, String reason) throws AlipayApiException {
        //生成退款单
        RefundInfo refundInfo = refundInfoService.createRefundInfo(orderNo, reason);

        //调用支付宝统一收单交易退款接口
        AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
        AlipayTradeRefundModel model = new AlipayTradeRefundModel();
        model.setRefundAmount(NumberUtil.toStr(NumberUtil.div(refundInfo.getTotalFee(), new Integer(100))));
        model.setOutTradeNo(orderNo);
        model.setRefundReason(reason);
        request.setBizModel(model);

        //支付宝退款接口是同步返回, 除了银行卡支付是异步通知
        AlipayTradeRefundResponse response = alipayClient.execute(request);
        System.out.println(response.getBody());

        OrderInfo orderInfo = new OrderInfo();
        if (response.isSuccess()) {
            System.out.println("调用成功");
            orderInfo.setOrderStatus(OrderStatus.REFUND_SUCCESS.getType());
            refundInfo.setRefundStatus(AliPayTradeState.REFUND_SUCCESS.getType());
        } else {
            System.out.println("调用失败");
            orderInfo.setOrderStatus(OrderStatus.REFUND_ABNORMAL.getType());
            refundInfo.setRefundStatus(AliPayTradeState.REFUND_ERROR.getType());
        }

        //更新本地退单记录数据
        refundInfo.setContentReturn(JSON.toJSONString(response));
        refundInfoService.updateById(refundInfo);

        //更新本地订单状态
        LambdaQueryWrapper<OrderInfo> wrapper = Wrappers.<OrderInfo>lambdaQuery().eq(OrderInfo::getOrderNo, orderNo);
        orderInfoService.update(orderInfo, wrapper);
    }

    @Override
    public String downloadBill(String billDate, String type) throws AlipayApiException {
        AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest();
        AlipayDataDataserviceBillDownloadurlQueryModel model = new AlipayDataDataserviceBillDownloadurlQueryModel();
        model.setBillType(type);
        model.setBillDate(billDate);
        request.setBizModel(model);
        AlipayDataDataserviceBillDownloadurlQueryResponse response = alipayClient.execute(request);
        System.out.println(response.getBody());
        if (response.isSuccess()) {
            System.out.println("调用成功");
            return response.getBillDownloadUrl();
        } else {
            System.out.println("调用失败");
        }
        return null;
    }

    @Override
    public String refundQuery(String orderNo) throws AlipayApiException {
        AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest();
        AlipayTradeFastpayRefundQueryModel model = new AlipayTradeFastpayRefundQueryModel();
        model.setOutTradeNo(orderNo);
        model.setOutRequestNo(orderNo);
        request.setBizModel(model);
        AlipayTradeFastpayRefundQueryResponse response = alipayClient.execute(request);
        if(response.isSuccess()){
            System.out.println("调用成功");
            return response.getBody();
        } else {
            System.out.println("调用失败");
            return "";
        }
    }

}
